<?php

class Model extends DB
{
    protected $tableName, $pk, $id, $attributes, $Key, $Field, $attributeNames;
    private static $db;

    // Magic functions
    public function __construct()
    {
        self::$db=Sys::app()->db;
    }

    public function setAttributes($attributes)
    {
        foreach($attributes as $name=>$value){
            $this->$name=$value;
        }
    }

    public function getAttributes()
    {
        $getFields=create_function('$object','return get_object_vars($object);');
        if($fields=$getFields($this)) return $fields;
        else return false;
    }

    public function getId()
    {
        if(property_exists($this,'id') && $this->id) return $this->id;
        elseif($pk=$this->getPk()) return $this->$pk ? $this->$pk : false;
    }

    public function getAttributeNames()
    {
        if($this->attributeNames===NULL) return $this->getColumnNames($this->tableName);
        else return $this->attributeNames;
    }

    public function setAttributeNames($newNames=array())
    {
        if($newNames && is_array($newNames)){
            $attributeNames=$this->attributeNames ? $this->attributeNames : $this->getColumnNames($this->tableName);

            foreach($attributeNames as $num=>$name){
                if(isset($newNames[$name])){
                    $newValue=new stdClass();
                    unset($attributeNames[$num]);

                    foreach($newNames[$name] as $param=>$value){
                        $newValue->$param=$value;
                    }

                    $attributeNames[]=$newValue;

                    unset($newNames[$name]);
                }
            }

            if($newNames){
                foreach($newNames as $name){
                    $attributeNames[]=$name;
                }
            }

            if($attributeNames) $this->attributeNames=$attributeNames;
        }
    }

    public function getCount($table='',$attributes=array())
    {
        return $this->getTableTotal($table);
    }

    // Public models methods
    public function findAll($attributes=array()){
        return $this->select($attributes);
    }

    public function findByPk($id=0,$attributes=array())
    {
        if(!$id){
            Sys::app()->error='Нет айдишника "0" в таблице '.$this->tableName().'! ';
            return false;
        }
        $table=$this->getTableName();

        if($attributes=$this->createAttributes($id,$attributes)){
            if(!$result=$this->getResult($attributes)){
                Sys::app()->error='В таблице '.$table.' не найдено ни одной записи под с id = '.$id;
                return false;
            }

            if(is_array($result)) return $result[0];
            elseif(is_object($result)) return $result;
            else return false;
        }else{
            Sys::app()->error='Необходимо указать Primary Key для таблицы '.$table;
            return false;
        }
    }

    public function findByAttributes($attributes=array())
    {
        $result=$this->select($attributes);
        if(is_array($result)) return $result[0];
        elseif(is_object($result)) return $result;
        else return false;
    }

    public function findAllByAttributes($attributes=array())
    {
        $result=$this->select($attributes);
        if(is_array($result)) return $result;
        elseif(is_object($result)) return array($result);
        else return false;
    }

    public function save($attributes=array(),$isNew=false)
    {
        if(!$attributes) $attributes=$this->getAttributes();
        if(!$attributes){
            Sys::app()->error='Нельзя записать в базу данных пустое место. Спасибо.';
            return false;
        }

        if($isNew || !$this->isRecordExist()){
            return $this->insert($attributes);
        }else{
            return $this->update($this->id,$attributes);
        }
    }

    public function update($id=0,$attributes=array())
    {
        if(!$attributes && is_array($id)){
            $attributes=$id;
            $id=0;
        }
		if(!$attributes) $attributes=$this->getAttributes();
        if(!$attributes){
            Sys::app()->error='Нельзя записать в базу данных пустое место. Спасибо.';
            return false;
        }
        if(!$id) $id=$this->getId();

        if($id && !$this->isRecordExist($id)){
            return $this->insert($attributes);
        }else{
            if($id) return $this->updateRecord($id,$attributes);
            else return $this->updateRecord($attributes);
        }
    }

    public function isNewRecord()
    {
        if(!$this->id) return true;
        else return !$this->isRecordExist($this->id);
    }

    // Protected Methods
    protected function getPk($table='')
    {
        if($this->pk) return $this->pk;
        if(!$table) $table=$this->getTableName();
        $result=array();

        $sql="SHOW COLUMNS FROM `".$table."`";

        if($query=self::$db->query($sql,get_class($this))){
            if(!is_array($query)) return false;

            foreach($query as $res){
                if($res->Key=='PRI') $result[]=$res->Field;
            }
        }

        if(sizeof($result)>1) return $result;
        elseif(sizeof($result)==1) return $result[0];
        else return false;
    }

    // Public static functions
    public static function model($model)
    {
        return Sys::app()->loadModel($model);
    }

    // Private SQL functions
    private function select($attributes=array(),$table='')
    {
        if($attributes) $attributes=self::checkAttributes($attributes);

        if(!$table) $table=$this->getTableName();

        if(!isset($attributes['attributes']) || !$attributes['attributes']) $attr=false;
        else $attr=$attributes['attributes'];

        if(!isset($attributes['where']) || !$attributes['where']) $where=$attributes;
        else $where=$attributes['where'];

        $attributeNames=$this->getColumnNames($table);
        if(!$attr && isset($attributes[0])){
            $tmpAttr=is_array($attributes[0]) ? $attributes[0] : array($attributes[0]);

            foreach($tmpAttr as $attrName){
                $attr[]=$attrName;
            }
        }

        $elseAttributes=array();
        if($where){

            $namesOfWhere=array_keys($where);
            if($different=array_diff($namesOfWhere,$attributeNames)){
                foreach($different as $attrName){
                    $elseAttributes[$attrName]=$where[$attrName];
                    unset($where[$attrName]);
                }
            }
        }

        $sql="SELECT ";

        if(!$attr){
            $sql.="* ";
        }else{
            $keys=array_keys($attr);
            foreach($attr as $name=>$value){
                if($name!=end($keys)) $sql.="`".$value."`, ";
                else $sql.="`".$value."` ";
            }
        }

        $sql.="FROM `".$table."`";

        $sql.=$this->createWhereAttributes($where);

        if($elseAttributes){
            $limit="";
            foreach($elseAttributes as $name=>$value){
                $bigName=strtoupper($name);
                if($bigName=='LIMIT'){
                    $limit.=" LIMIT ".$value;
                    continue;
                }elseif($bigName=='ORDER' || $bigName=='ORDER BY' || $bigName=='SORT' || $bigName=='SORT BY'){
                    $desc=' ASC';
                    if(strpos($value,' desc') || strpos($value,'desc')){
                        $value=str_replace(' desc','',$value);
                        $value=str_replace('desc','',$value);
                        $desc=' DESC';
                    }elseif(strpos($value,' DESC') || strpos($value,'DESC')){
                        $value=str_replace(' DESC','',$value);
                        $value=str_replace('DESC','',$value);
                        $desc=' DESC';
                    }elseif(strpos($value,' Desc') || strpos($value,'Desc')){
                        $value=str_replace(' Desc','',$value);
                        $value=str_replace('Desc','',$value);
                        $desc=' DESC';
                    }elseif(strpos($value,' asc')) $value=str_replace(' asc','',$value);
                    elseif(strpos($value,' ASC')) $value=str_replace(' ASC','',$value);
                    elseif(strpos($value,' Asc')) $value=str_replace(' Asc','',$value);
                    if(in_array($value,$attributeNames)) $sql.=" ORDER BY `".$value."`".$desc;
                }
            }
            $sql.=$limit;
        }

        return self::$db->query($sql,get_class($this));
    }

    private function insert($attributes=array(),$table='')
    {
        if(!$table) $table=$this->getTableName();
        if(!$attributes){
            Sys::app()->error='Попытка вставить пустые данные в таблицу '.$table;
            return false;
        }

        if($attributes=$this->createAttributes(0,$attributes)){
            $sql="";
            foreach($attributes as $attribute){
                $sql.=$this->createInsertSql($attribute).";";
            }
            return Sys::app()->db->query($sql);
        }else{
            Sys::app()->error='Попытка вставить пустые данные в таблицу '.$table;
            return false;
        }
    }

    private function updateRecord($id=0,$attributes=array())
    {
        if(!$attributes && is_array($id)){
            $attributes=$id;
            $id=0;
        }

        if($attributes=$this->createAttributes($id,$attributes)){
            $sql="";
            foreach($attributes as $attribute){
                if($command=$this->createUpdateSql($id,$attribute)) $sql.=$command.";";
            }

            if($sql) return Sys::app()->db->query($sql);
            else{
                Sys::app()->error='Неправильные параметры переданы для обновления таблицы '.$this->getTableName();
                return false;
            }
        }else{
            Sys::app()->error='Попытка обновить таблицу '.$this->getTableName().' пустыми значениями.';
            return false;
        }
    }

    // Private special functions
    private function createAttributes($id=0,$attributes=array())
    {
        $pk=$this->getPk();
        if(!$id) $id=$this->getId();

        if(!$pk) return false;

        $where=array();
        if(isset($attributes['where'])){
            $where=$attributes['where'];
            unset($attributes['where']);
        }

        $result=array();

        if(is_array($pk)){
            foreach($pk as $key){
                if($id) $conditions=array($key=>$id);
                else $conditions=array();
                if($where && $conditions) $conditions=array_merge($conditions,$where);
                else $conditions=$where;
                if($conditions){
                    $result[]=array(
                        'attributes'=>$attributes,
                        'where'=>$conditions,
                    );
                }else{
                    $result[]=array(
                        'attributes'=>$attributes,
                    );
                }
            }
        }else{
            if($id) $conditions=array($pk=>$id);
            else $conditions=array();
            if($where && $conditions) $conditions=array_merge($conditions,$where);
            elseif(!$conditions) $conditions=$where;

            if($conditions){
                $result[]=array(
                    'attributes'=>$attributes,
                    'where'=>$conditions,
                );
            }else{
                $result[]=array(
                    'attributes'=>$attributes,
                );
            }
        }

        return $result ? $result : false;
    }

    private function createInsertSql($attributes=array(),$table='')
    {
        if(!$table) $table=$this->getTableName();
        if(!$attributes || !isset($attributes['attributes'])) return false;
        if(!$attributes=$attributes['attributes'])  return false;

        $sql="INSERT INTO `".$table."` (" ;

        $columns=$this->getColumnNames();

        $trueAttributes=array();
        foreach($attributes as $name=>$value){
            if(is_array($value)){
                $trueAttributes=true;
                continue;
            }
            if(in_array($name,$columns)) $trueAttributes[$name]=$value;
        }

        if($trueAttributes===true){
            $names=array_keys(end($attributes));
            $sql.=$this->createNamesForInsertSql($names);
            $attributeNames=array_keys($attributes);
            foreach($attributes as $name=>$value){
                if($name!=end($attributeNames)){
                    $sql.=$this->createValuesForInsertSql($value).", ";
                }else{
                    $sql.=$this->createValuesForInsertSql($value);
                }
            }
        }elseif(is_array($trueAttributes) && sizeof($trueAttributes)){
            $sql.=$this->createNamesForInsertSql(array_keys($trueAttributes));
            $sql.=$this->createValuesForInsertSql($trueAttributes);
        }else{
            return false;
        }

        return $sql;
    }

    private function createUpdateSql($id=0,$attributes=array(),$table=''){
        if(!$table) $table=$this->getTableName();
        $pk=$this->getPk();

        $where=array();
        if(isset($attributes['where'])) $where=$attributes['where'];
        $attributes=$attributes['attributes'];

        if(!$where){
            if(is_array($attributes) && !is_array(end($attributes))){
                if(!$id) $id=$this->getId();
                if($id) $where=array($pk=>$id);
                else return false;
            }
        }

        $columnNames=$this->getColumnNames($table);

        $sql="";

        if(is_array(end($attributes))){
            $sql.="LOCK TABLE `".$table."` WRITE;";

            foreach($attributes as $id=>$attribute){
                if(!$where) $terms=array($pk=>$id);
                else $terms=$where;
                $sql.=" UPDATE `".$table."` SET ".$this->createValuesForUpdateSql($attribute,$columnNames)." WHERE ".$this->createValuesForUpdateSql($terms).";";
            }

            $sql.="UNLOCK TABLE `".$table."`";
        }else{
            $sql.=" UPDATE `".$table."` SET ".$this->createValuesForUpdateSql($attributes,$columnNames)." WHERE ".$this->createValuesForUpdateSql($where);
        }

        return $sql;
    }

    private function getResult($attributes=array())
    {
        if(!$attributes || !is_array($attributes)){
            Sys::app()->error='Укажите индексный ключ в модели '.get_class($this);
            return false;
        }

        $result=array();
        if(sizeof($attributes)>1){
            foreach($attributes as $attr){
                if($res=$this->select($attr)) $result[]=$res;
            }
        }else{
            if($res=$this->select($attributes[0])) $result[]=$res;
        }

        if($result && sizeof($result==1)) $result=$result[0];

        return $result;
    }

    private function getTableName()
    {
        if($this->tableName) return $this->tableName;
        elseif(method_exists($this,'tableName')) return $this->tableName();
        else return lcfirst(get_class($this));
    }

    private function isRecordExist($id=0,$table='')
    {
        if(!$id) $id=(int)$this->id;
        if(!$table) $table=$this->getTableName();
        if(!$this->pk) $this->pk=$this->getPk($table);

        if(!$id || !$this->pk || !$table) return false;

        $sql="SELECT 1 FROM `".$table."` WHERE `".$this->pk."` = '".$id."'";
        return Sys::app()->db->query($sql) ? true : false;
    }

    private function isTableExist($table='')
    {
        if(!$table) $table=$this->getTableName();
        $sql="SELECT 1 FROM `information_schema`.`TABLES` WHERE table_schema = '".$this->getDbName()."' AND table_name = '".$table."'";
        return Sys::app()->db->query($sql) ? true : false;
    }

    private function getTableTotal($table='',$params=array())
    {
        if(!$params && is_array($table)){
            $params=$table;
            $table='';
        }
        if(!$table) $table=$this->getTableName();

        $sql="SELECT COUNT(`".$this->getPk($table)."`) AS total FROM `".$table."`";

        if($params){
            $params=self::checkAttributes($params);
            $columnNames=$this->getColumnNames($table);
            foreach(array_keys($params) as $name){
                if(!in_array($name,$columnNames)) unset($params[$name]);
            }
            $sql.=$this->createWhereAttributes($params);
        }

        $result=Sys::app()->db->query($sql);

        if(is_array($result)) return $result[0]->total;
        elseif(is_object($result)) return $result->total;
        else return false;
    }

    private function getColumnNames($table='')
    {
        if(!$table) $table=$this->getTableName();
        $sql="SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '".$this->dbName."' AND TABLE_NAME = '".$table."'";

        if($query=self::$db->query($sql)){
            if(!is_array($query)) return false;

            $result=array();
            foreach($query as $res){
                $result[]=$res->COLUMN_NAME;
            }
            return $result;
        }
        return false;
    }

    private function createNamesForInsertSql($attributesNames){
        $sql="";
        foreach($attributesNames as $name){
            if($name!=end($attributesNames)){
                $sql.="`".$name."`, ";
            }else{
                $sql.="`".$name."`) VALUES ";
            }
        }
        return $sql;
    }

    private function createValuesForInsertSql($values)
    {
        $attributeNames=array_keys($values);
        $sql="(";
        foreach($values as $name=>$value){
            if($name!=end($attributeNames)){
                $sql.="'".$value."', ";
            }else{
                $sql.="'".$value."')";
            }
        }
        return $sql;
    }

    private function createValuesForUpdateSql($attributes=array(),$columnNames=array())
    {
        if(!$attributes || !is_array($attributes)){
            Sys::app()->error='Попытка обновить таблицу '.$this->getTableName().' пустыми значениями. ';
            return false;
        }

        $attributesNames=array_keys($attributes);
        $sql="";

        foreach($attributes as $name=>$value){
            if($columnNames && !in_array($name,$columnNames)) break;

            if($name!=end($attributesNames)){
                $sql.="`".$name."` = '".$value."', ";
            }else{
                $sql.="`".$name."` = '".$value."'";
            }
        }

        return $sql;
    }

    private function createWhereAttributes($attributes=array())
    {
        if(!$attributes) return '';
        $sql="";
        $keys=array_keys($attributes);
        foreach($attributes as $name=>$value){
            if(strpos($value,'like ')!==false || strpos($value,'like--')!==false){
                $value=str_replace('like ','',$value);
                $value=str_replace('like--','',$value);
                if($name!=end($keys)) $sql.="`".$name."` LIKE '%".$value."%' AND ";
                else $sql.="`".$name."` LIKE '%".$value."%'";
            }elseif(strpos($value,'like-')!==false || strpos($value,'like- ')!==false){
                $value=str_replace('like- ','',$value);
                $value=str_replace('like-','',$value);
                if($name!=end($keys)) $sql.="`".$name."` LIKE '%".$value."' AND ";
                else $sql.="`".$name."` LIKE '%".$value."'";
            }elseif(strpos($value,'like -')!==false || strpos($value,'like - ')!==false){
                $value=str_replace('like - ','',$value);
                $value=str_replace('like -','',$value);
                if($name!=end($keys)) $sql.="`".$name."` LIKE '".$value."%' AND ";
                else $sql.="`".$name."` LIKE '".$value."%'";
            }else{
                if($name!=end($keys)) $sql.="`".$name."` = '".$value."' AND ";
                else $sql.="`".$name."` = '".$value."'";
            }
        }
        return $sql ? " WHERE ".$sql : '';
    }
}